#pragma once

#include <vex/functional/result.h>
#include <utility>
#include <type_traits>

namespace vex { namespace functional { 
    
    namespace implementation {

        /// Represents a result value of a function or delegate call. Used to store 
        /// either defaults or values returned by a delegate call.
        template<class TRet> struct result_value {
            typedef TRet type;
            typedef TRet result_type;

            result_type operator()() const
            {
                return m_value;
            }

            template<class URet>
            result_value& operator=(URet&& p_value)
            {
                m_value = std::forward<URet>(p_value);
                return *this;
            }

            template<class URet>
            explicit result_value(URet&& p_value, typename std::enable_if<
                std::is_convertible<URet, TRet>::value>::type* = 0) 
                : m_value(std::forward<URet>(p_value)) {}
        private:
            TRet m_value;
        };

        /// Represents a result value of a function or delegate call. Used to store 
        // either defaults or values returned by a delegate call. Specialization for void
        template<> class result_value<void> {
        public:
            typedef void result_type;
            void operator()() const {}
        };

        /// Represents a result value of a function or delegate call. Used to store 
        // either defaults or values returned by a delegate call. Specialization for reference types
        template<class TRet> class result_value<TRet &> {
            typedef TRet* content_type;
        public:
            typedef TRet type;
            typedef TRet& result_type;
            TRet* m_value;

            result_type operator()() const
            {
                return *m_value;
            }

            result_value& operator=(TRet& p_value) 
            {
                m_value = std::addressof(p_value);
                return *this;
            }

            explicit result_value(TRet& p_value) 
                : m_value(std::addressof(p_value)) {}
        };

        /// <summary>
        /// Meta-fuction helper to create result_value types. Mainly to support arrays as argument types.
        /// </summary>
        template<class TRet> struct make_result_value {
            typedef TRet result_type;
            typedef result_value<TRet> type;
        };

        /// <summary>
        /// Meta-fuction helper to create result_value types. Mainly to support arrays as argument types.
        /// Allows to pass arrays as default values. Arrays should be constant and have a scope which
        /// is larger than the life-time of the delegate.
        /// </summary>
        template<class TRet, int Size> struct make_result_value<TRet (&) [Size]> {
            typedef TRet* result_type;
            typedef result_value<TRet*> type;
        };
    }

    /// Creates a default result_value for delegates with void return type
    inline auto default_value()
        -> implementation::result_value<void>
    {
        return implementation::result_value<void>();
    }

    /// <summary>
    /// Creates a default result_value for delegates with non-void return type.
    /// Must be called explicitly when creating a default multicast delegate of
    /// TSignature which has non-void return type.
    /// </summary>
    ///
    /// <param name="p_default">
    /// The default value returned when invocation list of the delegate is empty
    /// </param>
    template<class TRet> auto default_value(TRet&& p_default)
        -> typename implementation::make_result_value<TRet>::type
    {
        return typename implementation::make_result_value<TRet>::type(
            std::forward<TRet>(p_default));
    }
}}